/*
 *  Copyright 2019-2020 Zheng Jie
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package me.zhengjie.modules.system.service.impl;

import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.compress.CompressUtil;
import cn.hutool.extra.compress.archiver.Archiver;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import me.zhengjie.config.FileProperties;
import me.zhengjie.config.utils.PDFUtil;
import me.zhengjie.modules.system.domain.*;
import me.zhengjie.modules.system.repository.ExamDatabaseRepository;
import me.zhengjie.modules.system.service.AtmrService;
import me.zhengjie.modules.system.service.GroupTestService;
import me.zhengjie.modules.system.service.WxUserService;
import me.zhengjie.modules.system.service.dto.*;
import me.zhengjie.modules.system.service.mapstruct.ExamResultQueryMapper;
import me.zhengjie.modules.web.service.mybatisplus.IExamResultPlusService;
import me.zhengjie.modules.web.service.mybatisplus.IGroupTestDetailPlusService;
import me.zhengjie.modules.web.service.mybatisplus.IGroupTestPlusService;
import me.zhengjie.utils.*;
import lombok.RequiredArgsConstructor;
import me.zhengjie.modules.system.repository.ExamResultRepository;
import me.zhengjie.modules.system.service.ExamResultService;
import me.zhengjie.modules.system.service.mapstruct.ExamResultMapper;
import me.zhengjie.utils.StringUtils;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.zip.ScatterZipOutputStream;
import org.apache.commons.compress.utils.Lists;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.util.CollectionUtils;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.text.MessageFormat;
import java.util.*;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;

/**
 * @author zhaoxiancheng
 * @website https://el-admin.vip
 * @description 服务实现
 * @date 2021-11-18
 **/
@Service
@RequiredArgsConstructor
public class ExamResultServiceImpl implements ExamResultService {

    private final ExamResultRepository examResultRepository;
    private final ExamResultMapper examResultMapper;
    private final GroupTestService groupTestService;
    private final WxUserService wxUserService;
    private final AtmrService atmrService;

    private final ExamDatabaseRepository examDatabaseRepository;

    private final ExamResultQueryMapper examResultQueryMapper;

    @Autowired
    FileProperties properties;

    @Autowired
    IExamResultPlusService examResultPlusService;

    @Autowired
    private IGroupTestDetailPlusService groupTestDetailPlusService;

    @Autowired
    private IGroupTestPlusService groupTestPlusService;

    /**
     * @param groupId
     * @return
     * @see ExamResultService#countGroupByResultType(String)
     */
    @Override
    public List<ExamResultCountDto> countGroupByResultType(String groupId) {
        List<ExamResultCountDto> list = Lists.newArrayList();
        if (StringUtils.isBlank(groupId)) {
            return list;
        }
        List<Object[]> resultList = examResultRepository.countGroupByAtmrResultType(groupId);
        if (CollectionUtils.isEmpty(resultList)) {
            return list;
        }

        for (Object[] attr : resultList) {
            list.add(ExamResultCountDto.builder().resultType(attr[0].toString()).cnt(Integer.valueOf(attr[1].toString())).build());
        }

        return list;
    }

    @Override
    public Map<String, Object> queryAll(ExamResultQueryCriteria criteria, Pageable pageable) {
        Page<ExamResult> page = examResultRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder), pageable);
        Page<ExamResultDto> dtpPage = page.map(examResultMapper::toDto);
        setExamDatabaseName(dtpPage);
        return PageUtil.toPage(dtpPage);
    }

    /**
     * 查询题库名称
     *
     * @param dtpPage
     */
    private void setExamDatabaseName(Page<ExamResultDto> dtpPage) {
        if (dtpPage.getTotalElements() <= 0) {
            return;
        }
        List<ExamResultDto> list = dtpPage.getContent();
        Set<Long> examDatabaseIdSet = list.stream().map(ExamResultDto::getExamDatabaseId).collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(examDatabaseIdSet)) {
            return;
        }
        ExamDatabaseQueryCriteria queryCriteria = new ExamDatabaseQueryCriteria();
        queryCriteria.setDatabaseIdSet(examDatabaseIdSet);
        List<ExamDatabase> databaseList = examDatabaseRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, queryCriteria, criteriaBuilder));
        if (CollectionUtils.isEmpty(databaseList)) {
            return;
        }
        final Map<Long, String> databaseNameMap = databaseList.stream().collect(Collectors.toMap(ExamDatabase::getDatabaseId, ExamDatabase::getDatabaseName));
        list.forEach(s -> {
            s.setExamDatabaseName(databaseNameMap.get(s.getExamDatabaseId()));
        });
    }

    @Override
    public List<ExamResultDto> queryAll(ExamResultQueryCriteria criteria) {
        return examResultMapper.toDto(examResultRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root, criteria, criteriaBuilder)));
    }

    @Override
    @Transactional
    public ExamResultDto findById(Integer id) {
        ExamResult examResult = examResultRepository.findById(id).orElseGet(ExamResult::new);
        ValidationUtil.isNull(examResult.getId(), "ExamResult", "id", id);
        return examResultMapper.toDto(examResult);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public ExamResultDto create(ExamResult resources) {
        return examResultMapper.toDto(examResultRepository.save(resources));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void update(ExamResult resources) {
        ExamResult examResult = examResultRepository.findById(resources.getId()).orElseGet(ExamResult::new);
        ValidationUtil.isNull(examResult.getId(), "ExamResult", "id", resources.getId());
        examResult.copy(resources);
        examResultRepository.save(examResult);
    }

    @Override
    public void deleteAll(Integer[] ids) {
        for (Integer id : ids) {
            examResultRepository.deleteById(id);
        }
    }

    @Override
    public void download(List<ExamResultDto> all, HttpServletResponse response) throws IOException {
        List<GroupTestDto> groupTestDtos = groupTestService.queryAll(null);
        HashMap<Integer, GroupTestDto> mapGroup = new HashMap<>();
        for (GroupTestDto group : groupTestDtos) {
            mapGroup.put(group.getId(), group);
        }
        List<WxUserDto> wxUserDtos = wxUserService.queryAll(null);
        HashMap<String, WxUserDto> mapUser = new HashMap<>();
        for (WxUserDto user : wxUserDtos) {
            mapUser.put(user.getOpenId(), user);
        }

        List<AtmrDto> atmrDtos = atmrService.queryAll(null);
        HashMap<Integer, String> mapAtmrType = new HashMap<>();
        for (AtmrDto atmr : atmrDtos) {
            mapAtmrType.put(atmr.getId(), atmr.getAtmrName());
        }
        List<List> dataList = new ArrayList<List>();
        WxUserDto user = null;
        GroupTestDto group = null;
        String[] arrRes = null;
        String examResult = null;
        String newRes = null;
        ExamResultATMR atmr = null;
        List<ExamResultATMR> li = null;
        for (ExamResultDto result : all) {
            li = new ArrayList<>();
            newRes = "";
            user = mapUser.get(result.getWxId());
            List<Object> data = new ArrayList<>();
            data.add(result.getWxId());
            data.add(result.getWxName());
            data.add(null != user ? user.getCity() : "");
            data.add(result.getSex());
            data.add(null != user ? user.getMobile() : "");
            data.add(mapAtmrType.get(Integer.parseInt(result.getAtmrId())));
            data.add(result.getExamTime());
            if (org.apache.commons.lang3.StringUtils.isNotEmpty(result.getExamResult())) {
                examResult = result.getExamResult().replaceAll("\\.0", "");
                arrRes = examResult.split("\\,");
                atmr = new ExamResultATMR(arrRes[0], "A");
                li.add(atmr);
                atmr = new ExamResultATMR(arrRes[1], "T");
                li.add(atmr);
                atmr = new ExamResultATMR(arrRes[2], "M");
                li.add(atmr);
                atmr = new ExamResultATMR(arrRes[3], "R");
                li.add(atmr);

                Collections.sort(li);
                for (ExamResultATMR era : li) {
                    newRes += era.getCODE() + era.getSCORE() + ",";
                }
                data.add(newRes.substring(0, newRes.length() - 1));
                data.add(li.get(0).getCODE());
            }
            if (org.apache.commons.lang3.StringUtils.isNotEmpty(result.getGroupId())) {
                group = mapGroup.get(Integer.parseInt(result.getGroupId()));
                data.add(null != group ? group.getTitle() : "");
            } else {
                data.add("");
            }
            dataList.add(data);
        }
        List<String> titles = new ArrayList<String>();
        titles.add("序号"); // 1
        titles.add("OpenID"); // 2
        titles.add("微信昵称"); // 3
        titles.add("姓名"); // 4
        titles.add("性别"); // 5
        titles.add("联系电话"); // 6
        titles.add("测试类型"); // 6
        titles.add("测试时间"); // 7
        titles.add("测试结果"); // 9
        titles.add("潜意识"); // 10
        titles.add("团体测试标题"); // 11

        FileUtil.uploadExcelAboutUser(response, "Exam_result.xls", titles, dataList);
    }

    @Override
    public void export(List<String> pathList, HttpServletResponse response) throws IOException {
        response.reset();
        response.setHeader("Content-Disposition", "attachment;fileName=" + System.currentTimeMillis() + ".zip");
        response.setContentType("application/octet-stream;charset=UTF-8");
        Archiver archiver = CompressUtil.createArchiver(CharsetUtil.CHARSET_UTF_8, ArchiveStreamFactory.ZIP, response.getOutputStream());
        for (String path : pathList) {
            // /file/html/20211224/923935284276494336.html
            String[] strAttr = path.split("\\.");
            String tmpPath = path;
            File file = FileUtil.file(properties.getPath().getNgx() + tmpPath);
            if (file.isFile() && file.exists() && file.canRead()) {
                archiver.add(file);
                continue;
            }
        }

        archiver.finish().close();
    }

    @Override
    public String export_view(String fileUrl) throws IOException {
        String resultUrl = "";
        //Archiver archiver = CompressUtil.createArchiver(CharsetUtil.CHARSET_UTF_8, ArchiveStreamFactory.ZIP, response.getOutputStream());
        // /file/html/20211224/923935284276494336.html
        String[] strAttr = fileUrl.split("\\.");
        String pattern = "{0}_{1}.{2}";
        String tmpPath = MessageFormat.format(pattern, strAttr[0], "export", "pdf");
        File file = FileUtil.file(properties.getPath().getNgx() + tmpPath);
        if (file.isFile() && file.exists() && file.canRead()) {
            resultUrl = "http://wx.drzh-atmr.cn" + tmpPath;
        } else {
            PDFUtil.urlToPdf("http://wx.drzh-atmr.cn" + fileUrl, properties.getPath().getNgx() + tmpPath);
            file = FileUtil.file(properties.getPath().getNgx() + tmpPath);
            if (file.isFile() && file.exists() && file.canRead()) {
                resultUrl = "http://wx.drzh-atmr.cn" + tmpPath;
            }
        }
        return resultUrl;
    }

    public static void main(String[] args) {
        String path = "/file/html/20211224/923935284276494336.html";
        String[] strAttr = path.split("\\.");
        System.out.println(strAttr[0]);
        System.out.println(strAttr[1]);
        String pattern = "{0}_{1}.{2}";
        String tmpPath = MessageFormat.format(pattern, strAttr[0], "export", "pdf");
        System.out.println(tmpPath);
    }

    public void export1(List<String> pathList, HttpServletResponse response) throws IOException {
        response.reset();
        response.setHeader("Content-Disposition", "attachment;fileName=" + System.currentTimeMillis() + ".zip");
        response.setContentType("application/octet-stream;charset=UTF-8");
        Archiver archiver = CompressUtil.createArchiver(CharsetUtil.CHARSET_UTF_8, ArchiveStreamFactory.ZIP, response.getOutputStream());
        for (String path : pathList) {
            // /file/html/20211224/923935284276494336.html
            String[] strAttr = path.split("\\.");
            String pattern = "{0}_{1}.{2}";
            String tmpPath = MessageFormat.format(pattern, strAttr[0], "export", strAttr[1]);
            File file = FileUtil.file(properties.getPath().getNgx() + tmpPath);
            if (!file.isFile() || !file.exists() || !file.canRead()) {
                tmpPath = path;
            }
            file = FileUtil.file(properties.getPath().getNgx() + tmpPath);
            if (!file.isFile() || !file.exists() || !file.canRead()) {
                continue;
            }
            archiver.add(file);
        }

        archiver.finish().close();
    }

    @Override
    public ExamResult findByUuid(String uuid) {
        return examResultRepository.findByUuid(uuid);
    }

    @Override
    public List<ExamResult> findByOpenId(String[] openId) {
        return examResultRepository.findByOpenId(openId);
    }

    /**
     * 统计用户成人极速版测评首要特质数量
     *
     * @param openIdList
     * @return
     */
    @Override
    public List<ExamResultQueryDto> countUserSyTeZhiCnt(List<String> openIdList) {
        if (CollectionUtils.isEmpty(openIdList)) {
            return new ArrayList<>();
        }
        return examResultQueryMapper.countUserSyTeZhiCnt(openIdList);
    }

    @Override
    public ExamResultQueryTestDto querAdultTestInfo(String openId) {
        if (StringUtils.isEmpty(openId)) {
            return null;
        }
        return examResultQueryMapper.querAdultTestInfo(openId);
    }

    @Override
    public void exportGroupTest(Integer testId, HttpServletResponse response) throws IOException {

        if (Objects.isNull(testId)) {
            return;
        }
        LambdaQueryWrapper<ExamResult> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ExamResult::getGroupId, testId);
        List<ExamResult> examResultList = examResultPlusService.list(queryWrapper);
        if (CollectionUtils.isEmpty(examResultList)) {
            return;
        }
        GroupTestDto groupTestDto = groupTestService.findById(testId);
        String name = "团体测评";
        if (Objects.nonNull(groupTestDto)) {
            name = groupTestDto.getTitle();
        }
        response.reset();
        response.setHeader("Content-Disposition", "attachment;fileName=" + name + "_" + DateUtil.dateYYYYMMDDHHMMSS(new Date()) + ".zip");
        response.setContentType("application/octet-stream;charset=UTF-8");

        OutputStream outputStream = new BufferedOutputStream(response.getOutputStream(), 1024 * 1024);

        Archiver archiver = CompressUtil.createArchiver(CharsetUtil.CHARSET_UTF_8, ArchiveStreamFactory.ZIP, outputStream);

        for (ExamResult examResult : examResultList) {
            String reportUrl = examResult.getReportUrl();
            File file = FileUtil.file(properties.getPath().getNgx() + reportUrl);
            if (file.isFile() && file.exists() && file.canRead()) {
                archiver.add(file);
            }
        }
        archiver.finish().close();
        outputStream.flush();
        outputStream.close();
    }

    @Override
    public void exportGroupTestV2(Integer testId, HttpServletResponse response) throws IOException {
        if (Objects.isNull(testId)) {
            return;
        }
        LambdaQueryWrapper<ExamResult> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ExamResult::getGroupId, testId);
        List<ExamResult> examResultList = examResultPlusService.list(queryWrapper);
        if (CollectionUtils.isEmpty(examResultList)) {
            return;
        }
        GroupTestDto groupTestDto = groupTestService.findById(testId);
        String name = "团体测评";
        if (Objects.nonNull(groupTestDto)) {
            name = groupTestDto.getTitle();
        }
        name = URLEncoder.encode(name, "UTF-8");
        response.reset();
        response.setHeader("Content-Disposition", "attachment;fileName=" + name + "_" + DateUtil.dateYYYYMMDDHHMMSS(new Date()) + ".zip");
        response.setContentType("application/octet-stream;charset=UTF-8");

        // 使用更大的缓冲区
        int bufferSize = 1024 * 1024; // 1MB缓冲区
        try (OutputStream outputStream = new BufferedOutputStream(response.getOutputStream(), bufferSize);
             Archiver archiver = CompressUtil.createArchiver(CharsetUtil.CHARSET_UTF_8, ArchiveStreamFactory.ZIP, outputStream)) {

            // 使用并行流处理文件
            examResultList.stream().forEach(examResult -> {
                String reportUrl = examResult.getReportUrl();
                Path filePath = Paths.get(properties.getPath().getNgx() + reportUrl);
                try {
                    if (Files.isRegularFile(filePath) && Files.exists(filePath) && Files.isReadable(filePath)) {
                        synchronized(archiver) { // 确保线程安全
                            archiver.add(filePath.toFile());
                        }
                    }
                } catch (Exception e) {
                    // 记录日志，但继续处理其他文件
                    throw new RuntimeException(e);
                    // 如果需要继续处理其他文件，可以使用 throw new RuntimeException(e)
                    // 或者使用更合适的异常处理机制
                    // 这里改为记录日志并继续
                    // log.error("处理文件失败: " + filePath, e);
                }
            });

            archiver.finish();
            outputStream.flush();
        } catch (IOException e) {
            throw new RuntimeException("文件下载失败", e);
        }
    }

    @Override
    public void exportGroupTestQrcode(Integer testId, HttpServletResponse response) throws IOException {
        if (Objects.isNull(testId)) {
            return;
        }
        GroupTestDto groupTestDto = groupTestService.findById(testId);
        String name = "团体测评码";
        if (Objects.nonNull(groupTestDto)) {
            name = groupTestDto.getTitle();
        }
        name = URLEncoder.encode(name, "UTF-8");
        LambdaQueryWrapper<GroupTestDetail> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(GroupTestDetail::getGroupId, testId);
        List<GroupTestDetail> list = groupTestDetailPlusService.list(queryWrapper);
        response.reset();
        response.setHeader("Content-Disposition", "attachment;fileName=" + name + "_" + DateUtil.dateYYYYMMDDHHMMSS(new Date()) + ".zip");
        response.setContentType("application/octet-stream;charset=UTF-8");
        response.setContentType("application/zip");

        // 使用更大的缓冲区
        int bufferSize = 1024 * 1024; // 1MB缓冲区
        try (OutputStream outputStream = new BufferedOutputStream(response.getOutputStream(), bufferSize);
             Archiver archiver = CompressUtil.createArchiver(CharsetUtil.CHARSET_UTF_8, ArchiveStreamFactory.ZIP, outputStream)) {
            // 使用并行流处理文件
            list.stream().forEach(testDetail -> {
                String reportUrl = testDetail.getQrCodeUrl();
                String fileUrl = properties.getPath().getNgx() + reportUrl;
                Path filePath = Paths.get(fileUrl);
                try {
                    if (Files.exists(filePath)) {
                        synchronized(archiver) { // 确保线程安全
                            File file = filePath.toFile();
                            //file.renameTo(new File(file.getParent(), "编号_"+testDetail.getSerialNumber() + ".jpg"));
                            archiver.add(file);
                        }
                    }
                } catch (Exception e) {
                    // 记录日志，但继续处理其他文件
                    throw new RuntimeException(e);
                }
            });
            archiver.finish();
            outputStream.flush();
        } catch (IOException e) {
            throw new RuntimeException("文件下载失败", e);
        }
    }

    @Override
    public void batchExport(String groupCode, HttpServletResponse response) throws IOException {
        if (StringUtils.isBlank(groupCode)) {
            return;
        }
        LambdaQueryWrapper<ExamResult> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ExamResult::getGroupCode, groupCode);
        List<ExamResult> examResultList = examResultPlusService.list(queryWrapper);
        if (CollectionUtils.isEmpty(examResultList)) {
            return;
        }
        LambdaQueryWrapper<GroupTestDetail> detailWrapper = new LambdaQueryWrapper<>();
        detailWrapper.eq(GroupTestDetail::getSerialCode,groupCode);
        GroupTestDetail one = groupTestDetailPlusService.getOne(detailWrapper);
        GroupTest groupTestDto = groupTestPlusService.getById(one.getGroupId());
        String name =  groupTestDto.getTitle()+"_编号"+one.getSerialNumber();
        name = URLEncoder.encode(name, "UTF-8");
        response.reset();
        response.setHeader("Content-Disposition", "attachment;fileName=" + name + "_" + DateUtil.dateYYYYMMDDHHMMSS(new Date()) + ".zip");
        response.setContentType("application/octet-stream;charset=UTF-8");

        // 使用更大的缓冲区
        int bufferSize = 1024 * 1024; // 1MB缓冲区
        try (OutputStream outputStream = new BufferedOutputStream(response.getOutputStream(), bufferSize);
             Archiver archiver = CompressUtil.createArchiver(CharsetUtil.CHARSET_UTF_8, ArchiveStreamFactory.ZIP, outputStream)) {

            // 使用并行流处理文件
            examResultList.stream().forEach(examResult -> {
                String reportUrl = examResult.getReportUrl();
                Path filePath = Paths.get(properties.getPath().getNgx() + reportUrl);
                try {
                    if (Files.isRegularFile(filePath) && Files.exists(filePath) && Files.isReadable(filePath)) {
                        synchronized(archiver) { // 确保线程安全
                            archiver.add(filePath.toFile());
                        }
                    }
                } catch (Exception e) {
                    // 记录日志，但继续处理其他文件
                    throw new RuntimeException(e);
                }
            });

            archiver.finish();
            outputStream.flush();
        } catch (IOException e) {
            throw new RuntimeException("文件下载失败", e);
        }
    }
}